/*
 * @(#)CommunicationManager.java 10/12/2011 - 23:45:37
 * 
 * Copyright 2011 Cin/UFPE/SPEED Project
 */
package br.ufpe.cin.vspeed.integration;

import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.swing.JFrame;

import speed2010.mensagem.MsgTipo;
import speed2010.middleware.comunicacao.Address;
import speed2010.reformulacao.query.Query;
import speed2010.reformulacao.query.QueryResult;
import speed2010.util.SpeedClusterOntology;
import speed2010.util.SpeedLocalOntology;
import speed2010.util.Util;
import SPEED.ontologies.ClusterOntology;
import br.ufpe.cin.vspeed.util.FileUtils;
import br.ufpe.cin.vspeed.util.NetworkUtils;
import br.ufpe.cin.vspeed.util.PropertiesUtils;
import br.ufpe.cin.vspeed.util.SwingUtils;

/**
 * @author <a href="mailto:andreza.lba@gmail.com">Andreza Leite de Alencar</a>
 */
public class CommunicationManager {
	private static final String FILE_SEPARATOR = System.getProperty("file.separator");

	private static CommunicationManager instance;

	private NodeGeneric genericPeer;

	private boolean initialized;

	private boolean usingAnyOntology;

	private boolean prepared;

	static SpeedLocalOntology sourceOntology;

	static SpeedClusterOntology piOntology;
	private String piOntologyLocation;

	static Set<Address> registeredPeers;

	static Object mutex = new Object();

	static Map<Address, byte[]> piMapAddress;

	static Map<Address, QueryResult> queryResults;

	private CommunicationManager() throws IOException {
		PropertiesUtils.loadProperties("/speed.properties");
		registeredPeers = new LinkedHashSet<Address>();
		piMapAddress = new HashMap<Address, byte[]>();
	}

	public static CommunicationManager getInstance() {
		if (instance == null) {
			try {
				instance = new CommunicationManager();
			} catch (final IOException e) {
				throw new RuntimeException("Configuration file not found: " + "speed.properties");
			}
		}

		return instance;
	}

	/**
	 * @return Returns the genericPeer.
	 */
	public NodeGeneric getGenericPeer() {
		return this.genericPeer;
	}

	public void init() {
		try {
			final boolean connectionPrepared = CommunicationManager.getInstance().prepareConnection();
			final boolean messageSended = CommunicationManager.getInstance().sendRecoveryPDMessage();

			if (connectionPrepared && messageSended) {
				synchronized (CommunicationManager.mutex) {
					CommunicationManager.mutex.wait();
				}

				if (connectionPrepared && messageSended) {
					this.initialized = true;
				}
			}

			if (!messageSended) {
				SwingUtils.errorMessage(new JFrame(), "Unable to connect to SPEED netowork", "SPEED Network OFFLINE");
			}

		} catch (final Exception e) {
			e.printStackTrace();
		}
	}

	public boolean prepareConnection() throws Exception {
		boolean result = true;

		if (!isPrepared()) {
			String localAddress = null;
			Address nodeAddress = null;
			String genericPort = null;
			try {
				localAddress = NetworkUtils.getLocalHostIpAddress();

				String usingVPN = PropertiesUtils.getProperty("vpn", "0");
				String network = PropertiesUtils.getProperty("vpn.network");

				String systemVPN = System.getProperty("vpn");
				String systemNetwork = System.getProperty("vpn.network");

				if (systemVPN != null && systemNetwork != null) {
					usingVPN = systemVPN;
					network = systemNetwork;
				}

				if (usingVPN.equals("1")) {
					if (network != null) {
						localAddress = NetworkUtils.getHostIpAddressFromNetwork(network);
					}
				}

				genericPort = PropertiesUtils.getProperty("genericPort");
				nodeAddress = new Address(localAddress, Integer.valueOf(genericPort));

				this.genericPeer = new NodeGeneric(nodeAddress, null, null);
				this.prepared = true;
				this.genericPeer.start();
			} catch (final Exception e) {
				e.printStackTrace();
				result = false;
				throw new SocketException("Generic Peer unable to start in address: " + localAddress + "/"
						+ genericPort);
			}
		}

		return result;
	}

	public boolean isPD() {
		boolean result = false;

		if (CommunicationManager.registeredPeers != null) {
			if (!this.initialized) {
				init();
			}

			final Address pdAddress = getLocalPDAddress();

			if (pdAddress != null) {
				final Address piAddress = recoveryPIAddress(pdAddress);
				recoveryPSAddress(piAddress);
				this.usingAnyOntology = false;

				result = recoveryPDOntology(pdAddress);
			} else {
				final Address piAddress = recoveryPIAddress(getAnyAddress());
				recoveryPSAddress(piAddress);
			}
		}

		return result;
	}

	public boolean recoveryPDOntology(final Address pdAddress) {
		boolean result = false;

		synchronized (CommunicationManager.mutex) {
			try {
				CommunicationManager.sourceOntology = null;
				sendRecoveryOntoloyMessage(pdAddress);
				CommunicationManager.mutex.wait();

				if (CommunicationManager.sourceOntology != null) {
					final String folder = PropertiesUtils.getProperty("local.ontology");
					if (!(new File(folder).exists())) {
						new File(folder).mkdir();
					}

					final String fileName = "PeerOntology_" + pdAddress.getIP() + "_" + pdAddress.getPorta() + ".owl";
					final String completeFileName = folder + FILE_SEPARATOR + fileName;

					final byte[] bytes = CommunicationManager.sourceOntology.ontologia.getBytes();
					FileUtils.saveFile(bytes, completeFileName);

					CommunicationManager.sourceOntology = new SpeedLocalOntology(0, completeFileName, "nome");
					result = true;
				}
			} catch (final InterruptedException e) {
				e.printStackTrace();
			} catch (final IOException e) {
				e.printStackTrace();
			}
		}

		return result;
	}

	private boolean recoveryPIOntology(final Address piAddress) {
		boolean result = false;

		synchronized (CommunicationManager.mutex) {
			try {
				sendRecoveryPIOntoloyMessage(piAddress);
				CommunicationManager.mutex.wait();

				if (CommunicationManager.piOntology != null) {
					final String folder = PropertiesUtils.getProperty("local.ontology");
					if (!(new File(folder).exists())) {
						new File(folder).mkdir();
					}

					final String fileName = "PIOntology_" + piAddress.getIP() + "_" + piAddress.getPorta() + ".owl";
					final String completeFileName = folder + FILE_SEPARATOR + fileName;

					final byte[] bytes = CommunicationManager.piOntology.ontologia.getBytes();
					FileUtils.saveFile(bytes, completeFileName);
					this.piOntologyLocation = completeFileName;

					final String fullPath = new File(completeFileName).getAbsolutePath();

					final ClusterOntology clusterOntology = new ClusterOntology(0, fullPath, "nome");
					CommunicationManager.piOntology = new SpeedClusterOntology(clusterOntology);
					CommunicationManager.piMapAddress.put(piAddress, bytes);

					result = true;
				}
			} catch (final InterruptedException e) {
				e.printStackTrace();
			} catch (final IOException e) {
				e.printStackTrace();
			} catch (final Exception e) {
				e.printStackTrace();
			}
		}

		return result;
	}

	@SuppressWarnings("unused")
	private boolean recoveryPIOntologies(final Address piAddress) {
		final boolean result = false;

		synchronized (CommunicationManager.mutex) {
			try {
				sendRecoveryPSAddressMessage(piAddress);
				CommunicationManager.mutex.wait();

				if (this.genericPeer.getPsAddress() != null) {
					sendRecoveryPIListMessage(this.genericPeer.getPsAddress());
					CommunicationManager.mutex.wait();

					if (CommunicationManager.piMapAddress != null) {
						for (final Address address : CommunicationManager.piMapAddress.keySet()) {
							recoveryPIOntology(address);
						}
					}
				}
			} catch (final InterruptedException e) {
				e.printStackTrace();
			} catch (final Exception e) {
				e.printStackTrace();
			}
		}

		return result;
	}

	public boolean recoveryTopologyInfo() {
		boolean result = false;
		if (CommunicationManager.registeredPeers != null) {
			if (!this.initialized) {
				init();
			}

			synchronized (CommunicationManager.mutex) {
				try {
					sendRecoveryPSTopologyMessage(this.genericPeer.getPsAddress());
					CommunicationManager.mutex.wait();

					if (this.genericPeer.getMapAddressReduzido_NodeInfo() != null) {
						result = true;
					}
				} catch (final InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		return result;
	}

	Address recoveryPIAddress(final Address pdAddress) {
		Address result = null;

		if (CommunicationManager.registeredPeers != null) {
			if (!this.initialized) {
				init();
			}

			synchronized (CommunicationManager.mutex) {
				try {
					sendRecoveryPIAddressMessage(pdAddress);
					CommunicationManager.mutex.wait();

					if (this.genericPeer.getPiAddress() != null) {
						result = this.genericPeer.getPiAddress();

						sendRecoveryPSAddressMessage(this.genericPeer.getPiAddress());
						CommunicationManager.mutex.wait();
					}

				} catch (final InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		return result;
	}

	Address recoveryPSAddress(final Address piAddress) {
		Address result = null;

		if (CommunicationManager.registeredPeers != null) {
			if (!this.initialized) {
				init();
			}

			sendRecoveryPSAddressMessage(piAddress);
			synchronized (CommunicationManager.mutex) {
				try {
					CommunicationManager.mutex.wait();

					if (this.genericPeer.getPsAddress() != null) {
						result = this.genericPeer.getPsAddress();
					}
				} catch (final InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		return result;
	}

	public boolean sendRecoveryPDMessage() {
		boolean result = true;

		try {
			String nameServerIP = PropertiesUtils.getProperty("nameserver.ip");
			String nameServerPort = PropertiesUtils.getProperty("nameserver.port");

			String systemNameServerIP = System.getProperty("nameserver.ip");
			String systemNameServerPort = System.getProperty("nameserver.port");

			if (systemNameServerIP != null && systemNameServerPort != null) {
				nameServerIP = systemNameServerIP;
				nameServerPort = systemNameServerPort;
			}

			if (!NetworkUtils.isConnectionAlive(nameServerIP, nameServerPort)) {
				return false;
			}

			this.genericPeer.sendMsg(nameServerIP, Integer.valueOf(nameServerPort), MsgTipo.GET_REGISTER_PD_LIST);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	public boolean sendRecoveryOntoloyMessage(final Address address) {
		boolean result = true;

		try {
			this.genericPeer.sendMsg(address.getIP(), address.getPorta(), MsgTipo.GET_PD_ONTOLOGY);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	public boolean sendRecoveryPIAddressMessage(final Address address) {
		boolean result = true;

		try {
			this.genericPeer.sendMsg(address.getIP(), address.getPorta(), MsgTipo.GET_PI_ADDRESS);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	public boolean sendRecoveryPIOntoloyMessage(final Address piAddress) {
		boolean result = true;

		try {
			this.genericPeer.sendMsg(piAddress.getIP(), piAddress.getPorta(), MsgTipo.GET_PI_CLUSTER_ONTOLOGY);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	public boolean sendRecoveryPSAddressMessage(final Address address) {
		boolean result = true;

		try {
			this.genericPeer.sendMsg(address.getIP(), address.getPorta(), MsgTipo.GET_PS_ADDRESS);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	private boolean sendRecoveryPSTopologyMessage(final Address address) {
		boolean result = true;

		try {
			final long idSolicitacao = Util.geraID();
			this.genericPeer.sendMsg(address, MsgTipo.GET_PS_TOLOGY, idSolicitacao);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	public boolean sendRecoveryPIListMessage(final Address psAddress) {
		boolean result = true;

		try {
			this.genericPeer.sendMsg(psAddress.getIP(), psAddress.getPorta(), MsgTipo.GET_PI_LIST);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	public boolean sendPrepareTopolyMessage() {
		boolean result = true;

		try {
			final long idSolicitacao = Util.geraID();
			this.genericPeer.sendMsg(this.genericPeer.getAddress(), MsgTipo.MULTICAST_VIRTUALNODEINFO_ASK,
					idSolicitacao);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	Address getLocalPDAddress() {
		Address pdAddress = null;
		for (final Address address : CommunicationManager.registeredPeers) {
			final boolean isLocalIpAddress = address.getIP().equals(NetworkUtils.getLocalHostIpAddress());
			final boolean isLocalHostIp = address.getIP().equals("127.0.0.1");
			final boolean isLocalHost = address.getIP().equals("localhost");

			if (isLocalIpAddress || isLocalHostIp || isLocalHost) {
				pdAddress = address;
				break;
			}
		}

		return pdAddress;
	}

	Address getAnyAddress() {
		Address pdAddress = null;

		if (!CommunicationManager.registeredPeers.isEmpty()) {
			pdAddress = CommunicationManager.registeredPeers.iterator().next();
		}

		return pdAddress;
	}

	public Map<Address, QueryResult> submitQuery(final Query query) {
		final Map<Address, QueryResult> recoveryQueryResults = recoveryQueryResults(query);

		return recoveryQueryResults;
	}

	private Map<Address, QueryResult> recoveryQueryResults(final Query query) {
		Map<Address, QueryResult> result = null;

		Address piAddress = this.genericPeer.getPiAddress();

		if (piAddress == null) {
			final Address localPDAddress = getLocalPDAddress();

			if (localPDAddress == null) {
				piAddress = recoveryPIAddress(getAnyAddress());
			} else {
				piAddress = recoveryPIAddress(localPDAddress);
			}
		}

		if (piAddress != null) {
			synchronized (CommunicationManager.mutex) {
				query.setSourceOntology(CommunicationManager.sourceOntology.ontologia.getBytes());
				sendQueryMessage(query, piAddress);
				try {
					CommunicationManager.mutex.wait();

					if (CommunicationManager.queryResults != null) {
						result = CommunicationManager.queryResults;
					}
				} catch (final InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		return result;
	}

	private boolean sendQueryMessage(final Query query, final Address address) {
		boolean result = true;

		try {
			this.genericPeer.sendQueryMsg(address, query);
		} catch (final Exception e) {
			e.printStackTrace();
			result = false;
		}

		return result;
	}

	public SpeedLocalOntology getSourceOntology() {
		return CommunicationManager.sourceOntology;
	}

	public String getDestinyOntology() {
		return this.piOntologyLocation;
	}

	/**
	 * @return Returns the prepared.
	 */
	public boolean isPrepared() {
		return this.prepared;
	}

	/**
	 * @return Returns the initialized.
	 */
	public boolean isInitialized() {
		return this.initialized;
	}

	/**
	 * @return Returns the usingAnyOntology.
	 */
	public boolean isUsingAnyOntology() {
		return usingAnyOntology;
	}

	/**
	 * Changes the value of usingAnyOntology as the parameter.
	 * 
	 * @param usingAnyOntology
	 */
	public void setUsingAnyOntology(boolean usingAnyOntology) {
		this.usingAnyOntology = usingAnyOntology;
	}
}